#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_ttf.h>
#include <iostream>

/*
    手柄不支持，代码可靠性未经验证
*/
//Button constants
// 单个区域的宽和高
const int BUTTON_WIDTH = 300;
const int BUTTON_HEIGHT = 200;
const int TOTAL_BUTTONS = 4;

//模拟操纵杆死区
/*
SDL 处理游戏控制器上模拟摇杆的方式是将其位置转换为 -32768 至 32767 之间的数字。这意味着轻点一下就能报告 1000+ 的位置。
要忽略轻击，因此要创建一个死区，忽略来自手柄的输入。这就是定义这个常量的原因
*/
const int JOYSTICK_DEAD_ZONE = 8000;

//Game Controller 1 handler
// 游戏控制器的数据类型是 SDL_Joystick。在此，我们将声明用于与手柄交互的全局手柄句柄。 
// SDL_Joystick* gGameController = nullptr;

/*
使用 SDL_GameController，它是一个集控制器/隆隆声接口于一身的应用程序。再使用 SDL 操纵杆和触觉来支持旧版 API
*/
//带力反馈的游戏控制器处理程序
SDL_GameController* gGameController = nullptr;

//带触觉的操纵杆控制器
SDL_Joystick* gJoystick = nullptr;
SDL_Haptic* gJoyHaptic = nullptr;

enum LButtonSprite
{
    BUTTON_SPRITE_MOUSE_OUT,
    BUTTON_SPRITE_MOUSE_OVER_MOTION,
    BUTTON_SPRITE_MOUSE_DOWN,
    BUTTON_SPRITE_MOUSE_UP,
    BUTTON_SPRITE_TOTAL
};

// 封装SDL_Texture
//Texture wrapper class
class LTexture
{
    public:
        //Initializes variables
        LTexture();

        //Deallocates memory
        ~LTexture();

        //Loads image at specified path
        bool loadFromFile( std::string path );

        #if defined(SDL_TTF_MAJOR_VERSION)
        /*
        如果头文件不包含 SDL_ttf.h，编译器就会忽略它。它检查是否定义了 SDL_TTF_MAJOR_VERSION 宏。
        与 #include 一样，#if 也是用于与编译器对话的宏。在本例中，它表示如果 SDL_ttf 未定义，则忽略这段代码
        */
        //Creates image from font string
        // SDL_ttf 的工作方式是根据字体和颜色创建新图像。对于纹理类来说，这意味着将从 SDL_ttf 渲染的文本而不是文件中加载图像
        bool loadFromRenderedText( std::string textureText, SDL_Color textColor );
        #endif

        //Deallocates texture
        void free();

        //设置调制颜色，接收红、绿、蓝三色分量
        void setColor( Uint8 red, Uint8 green, Uint8 blue );

        //设置纹理混合  它将控制纹理的混合方式。为了使混合正常工作，必须设置纹理的混合模式
        void setBlendMode( SDL_BlendMode blending );

        //设置纹理的 alpha 透明度
        void setAlpha( Uint8 alpha );

        //Renders texture at given point
        /*接受一个矩形参数，用来定义我们要渲染的纹理部分。我们给它一个默认参数 nullptr，以防我们想要渲染整个纹理
          接受旋转角度、纹理旋转点和 SDL 翻转枚举值，同样也给出了参数的默认值，以备在不旋转或翻转的情况下渲染纹理。
        */
        void render( int x, int y, SDL_Rect* clip = nullptr, double angle = 0.0, SDL_Point* center = nullptr, SDL_RendererFlip flip = SDL_FLIP_NONE );

        //Gets image dimensions
        int getWidth();
        int getHeight();

    private:
        //实际渲染的纹理
        SDL_Texture* mTexture;

        //图像的宽和高
        int mWidth;
        int mHeight;
};

// 渲染的窗口
SDL_Window * gWindow = nullptr;

// 当我们处理 SDL 纹理时，需要一个 SDL_Renderer 将其渲染到屏幕上
//The window renderer
SDL_Renderer* gRenderer = nullptr;

int SCREEN_WIDTH = 640;
int SCREEN_HEIGHT = 480;

//使用的字体
TTF_Font* gFont = nullptr;

// 场景精灵
SDL_Rect gSpriteClips[ BUTTON_SPRITE_TOTAL ];

// LTexture gTextTexture;
// LTexture gButtonSpriteSheetTexture;
// LTexture gUpTexture;
// LTexture gDownTexture;
// LTexture gLeftTexture;
// LTexture gRightTexture;
// LTexture gPressTexture;
// LTexture gArrowTexture;
LTexture gSplashTexture;

LTexture::LTexture()
{
    //Initialize
    mTexture = nullptr;
    mWidth = 0;
    mHeight = 0;
}

LTexture::~LTexture()
{
    //Deallocate
    free();
}

void LTexture::setColor( Uint8 red, Uint8 green, Uint8 blue )
{
    //纹理颜色调制
    /*
    DL_SetTextureColorMod 接受 Uint8 作为颜色组件的参数。Uint8 只是一个无符号的 8 位整数。这意味着它的范围从 0 到 255。
    128 大约介于 0 和 255 之间，因此当您将绿色调制为 128 时，纹理上任何像素的绿色分量都会减半。
    红色和蓝色方格不会受到影响，因为它们没有绿色，但绿色的亮度会减半，白色则会变成浅洋红色（洋红色为红色 255、绿色 0、蓝色 255）。
    颜色调制只是将一种颜色乘以整个纹理的一种方法
    */
    SDL_SetTextureColorMod( mTexture, red, green, blue );
}

#if defined(SDL_TTF_MAJOR_VERSION)
bool LTexture::loadFromRenderedText( std::string textureText, SDL_Color textColor )
{
    ////先取消原有纹理的渲染
    free();

    //Render text surface
    /*
    根据字体创建要渲染的纹理。该函数接收要渲染的文本字符串和要用来渲染它的颜色。在此之后，该函数的工作原理与从文件加载相似，
    只不过这次使用的是由 SDL_ttf 创建的 SDL_Surface，而不是文件
    使用 TTF_RenderText_Solid 加载一个曲面。函数会根据给出的字体、文本和颜色创建一个纯色曲面。如果曲面创建成功，
    就会像之前从文件加载曲面一样创建纹理。创建纹理后，我们就可以像渲染其他纹理一样对其进行渲染。
    */
    SDL_Surface* textSurface = TTF_RenderText_Solid( gFont, textureText.c_str(), textColor );
    if( textSurface == nullptr )
    {
        std::cout << "Unable to render text surface! SDL_ttf Error:" << TTF_GetError() << std::endl;
    }
    else
    {
        //Create texture from surface pixels
        mTexture = SDL_CreateTextureFromSurface( gRenderer, textSurface );
        if( mTexture == nullptr )
        {
            std::cout << "Unable to create texture from rendered text! SDL Error:" << SDL_GetError() << std::endl;
        }
        else
        {
            //Get image dimensions
            mWidth = textSurface->w;
            mHeight = textSurface->h;
        }

        //Get rid of old surface
        SDL_FreeSurface( textSurface );
    }
    
    //Return success
    return mTexture != NULL;
}
#endif

bool LTexture::loadFromFile( std::string path )
{
    //先取消原有纹理的渲染
    free();

    //The final texture
    SDL_Texture* newTexture = nullptr;

    //Load image at specified path
    SDL_Surface* loadedSurface = IMG_Load( path.c_str() );
    if( loadedSurface == nullptr )
    {
        std::cout<< "Unable to load image "<< path.c_str() << "!, SDL_image Error: " << IMG_GetError() << std::endl;
    }
    else
    {
        /*  使用 SDL_SetColorKey 对图像进行去除背景。第一个参数是我们要进行去除背景处理的表面，第二个参数是我们是否要启用去除背景处理，
        最后一个参数是我们要进行去除背景处理的像素(图像中哪种颜色要去除)
            从 RGB 颜色创建像素的方法是使用 SDL_MapRGB。第一个参数是我们想要的像素格式(SDL_PixelFormat 结构)。幸运的是，载入的曲面中有一个成员变量可以获取该格式。
         最后三个变量是要映射的颜色的红、绿、蓝分量。
         青色 0，255，255
         黑色 0，0，0
         白色 255，255，255
        */
        SDL_SetColorKey( loadedSurface, SDL_TRUE, SDL_MapRGB( loadedSurface->format, 0xff, 0xff, 0xff ) );

        //Create texture from surface pixels
        newTexture = SDL_CreateTextureFromSurface( gRenderer, loadedSurface );
        if( newTexture == nullptr )
        {
            std::cout << "Unable to create texture from " << path.c_str() << "! SDL Error: " << SDL_GetError() << std::endl;
        }
        else
        {
            //Get image dimensions
            mWidth = loadedSurface->w;
            mHeight = loadedSurface->h;
        }

        //清楚旧纹理加载
        SDL_FreeSurface( loadedSurface );
    }

    //Return success
    mTexture = newTexture;
    return mTexture != nullptr;
}

void LTexture::free()
{
    //Free texture if it exists
    if( mTexture != nullptr )
    {
        SDL_DestroyTexture( mTexture );
        mTexture = nullptr;
        mWidth = 0;
        mHeight = 0;
    }
}

void LTexture::render( int x, int y, SDL_Rect* clip, double angle, SDL_Point* center, SDL_RendererFlip flip )
{
    //设置渲染区域并渲染到屏幕
    SDL_Rect renderQuad = { x, y, mWidth, mHeight };
    /* 在特定位置渲染纹理时，需要指定一个目标矩形，该矩形可设置 x/y 位置和宽度/高度。在不知道原始图像尺寸的情况下，我们无法指定宽度/高度。
    因此，当我们渲染纹理时，我们会创建一个包含位置参数和成员宽度/高度的矩形，并将此矩形传递给 SDL_RenderCopy
    */

    //Set clip rendering dimensions
    if( clip != nullptr )
    {
        renderQuad.w = clip->w;
        renderQuad.h = clip->h;
    }
    /*
    在剪辑时，如果使用的是剪辑矩形的尺寸而不是纹理的尺寸，我们将把目标矩形（此处称为 renderQuad）的宽度/高度设置为剪辑矩形的尺寸。
    我们要将剪辑矩形作为源矩形传递给 SDL_RenderCopy。源矩形定义了要渲染纹理的哪一部分。当源矩形为空时，将渲染整个纹理。
    */

    //Render to screen
    // SDL_RenderCopy( gRenderer, mTexture, clip, &renderQuad );
    //Render to screen 该函数的工作原理与原始的 SDL_RenderCopy 相同，但增加了用于旋转和翻转的参数
    SDL_RenderCopyEx( gRenderer, mTexture, clip, &renderQuad, angle, center, flip );
}

int LTexture::getWidth()
{
    return mWidth;
}

int LTexture::getHeight()
{
    return mHeight;
}

//鼠标按键
class LButton
{
    public:
        //Initializes internal variables
        LButton();

        //位置设置函数
        void setPosition( int x, int y );

        //用于事件循环的事件处理函数
        void handleEvent( SDL_Event* e );
    
        //渲染函数
        void render();

    private:
        //位置枚举
        SDL_Point mPosition;

        //精灵枚举
        LButtonSprite mCurrentSprite;
};

LButton gButtons[TOTAL_BUTTONS];

LButton::LButton()
{
    mPosition.x = 0;
    mPosition.y = 0;

    mCurrentSprite = BUTTON_SPRITE_MOUSE_OUT;
}

void LButton::setPosition( int x, int y )
{
    mPosition.x = x;
    mPosition.y = y;
}

// 该函数将在事件循环中调用，处理从事件队列中提取的单个按钮事件
void LButton::handleEvent( SDL_Event* e )
{
    //If mouse event happened
    // 首先，检查输入的事件是否是鼠标事件，具体包括鼠标移动事件（鼠标移动时）、鼠标按键下移事件（点击鼠标按键时）或鼠标按键上移事件（释放鼠标点击时）
    if( e->type == SDL_MOUSEMOTION || e->type == SDL_MOUSEBUTTONDOWN || e->type == SDL_MOUSEBUTTONUP )
    {
        //Get mouse position
        // 如果发生了这些鼠标事件之一，使用 SDL_GetMouseState 检查鼠标位置。根据鼠标是否位于按钮上方，我们需要显示不同的精灵
        int x, y;
        SDL_GetMouseState( &x, &y );

        //Check if mouse is in button
        /*
        检查鼠标是否位于按钮内。由于用的是与 SDL 不同的坐标系，因此按钮的原点位于左上方。
        这意味着每一个小于 x 位置的 x 坐标都在按钮外，每一个小于 y 位置的 y 坐标也在按钮外。按钮右侧的所有内容都是 x 位置 + 宽度，按钮下方的所有内容都是 y 位置 + 高度。
        这就是这段代码的作用。如果鼠标位置超出了按钮的任何范围，它就会将内部标记标记为假。否则，它将保持初始值为真
        */
        bool inside = true;

        //Mouse is left of the button
        if( x < mPosition.x )
        {
            inside = false;
        }
        //Mouse is right of the button
        else if( x > mPosition.x + BUTTON_WIDTH )
        {
            inside = false;
        }
        //Mouse above the button
        else if( y < mPosition.y )
        {
            inside = false;
        }
        //Mouse below the button
        else if( y > mPosition.y + BUTTON_HEIGHT )
        {
            inside = false;
        }

        //Mouse is outside button
        /*
        根据鼠标是否位于按钮内和鼠标事件来设置按钮精灵。
        如果鼠标不在按钮内，设置鼠标移出精灵。如果鼠标在按钮内，将精灵设置为鼠标移动时鼠标移到上方，
        鼠标按下时鼠标移到下方，鼠标释放时鼠标移到上方
        */
        if( !inside )
        {
            mCurrentSprite = BUTTON_SPRITE_MOUSE_OUT;
        }
        //Mouse is inside button
        else
        {
            //Set mouse over sprite
            switch( e->type )
            {
                case SDL_MOUSEMOTION:
                mCurrentSprite = BUTTON_SPRITE_MOUSE_OVER_MOTION;
                break;
            
                case SDL_MOUSEBUTTONDOWN:
                mCurrentSprite = BUTTON_SPRITE_MOUSE_DOWN;
                break;
                
                case SDL_MOUSEBUTTONUP:
                mCurrentSprite = BUTTON_SPRITE_MOUSE_UP;
                break;
            }
        }
    }
}

// 根据当前的鼠标事件(mCurrentSprite)渲染不同的图像(gSpriteClips)
void LButton::render()
{
    //Show current button sprite
    // gButtonSpriteSheetTexture.render( mPosition.x, mPosition.y, &gSpriteClips[ mCurrentSprite ] );
}

bool init()
{
     //Initialization flag
    bool success = true;

    //Initialize SDL
    // 增加初始化操纵杆子系统，否则从操纵杆读取数据将无法正常工作，初始化触觉和游戏控制器专用子系统，才能使用它们
    if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER ) < 0 )
    {
        char str[1024];
        sprintf(str, "SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
        std::cout << str << std::endl;
        success = false;
    }
    else
    {
        //Create window
        gWindow = SDL_CreateWindow( "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
        if( gWindow == nullptr )
        {
            char str[1024];
            sprintf(str, "Window could not be created! SDL_Error: %s\n", SDL_GetError());
            std::cout << str << std::endl;
            success = false;
        }
        else
        {
            //Create renderer for window
            // gRenderer = SDL_CreateRenderer( gWindow, -1, SDL_RENDERER_ACCELERATED );
            //为窗口创建垂直同步渲染器
            /*
            垂直同步,VSync 可以让渲染与显示器在垂直刷新时的更新同步进行。在本教程中，它将确保动画的运行速度不会太快。
            大多数显示器的运行速度约为每秒 60 帧，这也是我们的假设。如果你的显示器刷新率不同，这就能解释为什么动画运行得过快或过慢
            */
            gRenderer = SDL_CreateRenderer( gWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC );
            if( gRenderer == nullptr )
            {
                std::cout << "Renderer could not be created! SDL Error:" << SDL_GetError() << std::endl;
                success = false;
            }
            else
            {
                //Initialize renderer color
                SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF );

                //Initialize PNG loading
                int imgFlags = IMG_INIT_PNG;
                if( !( IMG_Init( imgFlags ) & imgFlags ) )
                {
                    std::cout << "SDL_image could not initialize! SDL_image Error:" << IMG_GetError() << std::endl;
                    success = false;
                }

                //Initialize SDL_ttf
                if( TTF_Init() == -1 )
                {
                    std::cout << "SDL_ttf could not initialize! SDL_ttf Error:" << TTF_GetError() << std::endl;
                    success = false;
                }
            }

            /*
            初始化操纵杆子系统后，打开操纵杆。首先，调用 SDL_NumJoysticks 检查是否至少连接了一个操纵杆。
            如果有，调用 SDL_JoystickOpen 打开索引为 0 的操纵杆。操纵杆打开后，它将向 SDL 事件队列报告事件。
            */
            //Set texture filtering to linear
            if( !SDL_SetHint( SDL_HINT_RENDER_SCALE_QUALITY, "1" ) )
            {
                std::cout << "Warning: Linear texture filtering not enabled!" << std::endl;
            }

            //Check for joysticks
            if( SDL_NumJoysticks() < 1 )
            {
                std::cout << "Warning: No joysticks connected!" << std::endl;
            }
            else
            {
                // //Load joystick
                // gGameController = SDL_JoystickOpen( 0 );
                // if( gGameController == NULL )
                // {
                //     std::cout << "Warning: Unable to open game controller! SDL Error: " << SDL_GetError() << std::endl;
                // }
                //检查第一个操纵杆是否与游戏控制器接口兼容
                if( !SDL_IsGameController( 0 ) )
                {
                    std::cout << "Warning: Joystick is not game controller interface compatible! SDL Error: " << SDL_GetError() << std::endl;
                }
                else
                {
                    //Open game controller and check if it supports rumble
                    gGameController = SDL_GameControllerOpen( 0 );
                    if( !SDL_GameControllerHasRumble( gGameController ) )
                    {
                        std::cout << "Warning: Game controller does not have rumble! SDL Error: " << SDL_GetError() << std::endl;
                    }
                }

                //如果无法加载游戏控制器，则加载操纵杆
                /*
                如果手柄与游戏控制器接口不兼容，我们会将其加载为普通手柄。
                打开手柄后，需要在已打开的手柄上使用 SDL_HapticOpenFromJoystick 从手柄获取触觉设备。
                如果设法从控制器中获取了触觉设备，必须使用 SDL_HapticRumbleInit 来初始化隆隆声。
                */
                if( gGameController == nullptr )
                {
                    //Open first joystick
                    gJoystick = SDL_JoystickOpen( 0 );
                    if( gJoystick == nullptr )
                    {
                        std::cout << "Warning: Unable to open joystick! SDL Error: " << SDL_GetError() << std::endl;
                    }
                    else
                    {
                        //检查操纵杆是否支持触觉
                        if( !SDL_JoystickIsHaptic( gJoystick ) )
                        {
                            std::cout << "Warning: Controller does not support haptics! SDL Error: " << SDL_GetError() << std::endl;
                        }
                        else
                        {
                            //获取操纵杆触觉装置
                            gJoyHaptic = SDL_HapticOpenFromJoystick( gJoystick );
                            if( gJoyHaptic == nullptr )
                            {
                                std::cout << "Warning: Unable to get joystick haptics! SDL Error: " << SDL_GetError() << std::endl;
                            }
                            else
                            {
                                //Initialize rumble
                                if( SDL_HapticRumbleInit( gJoyHaptic ) < 0 )
                                {
                                    std::cout << "Warning: Unable to initialize haptic rumble! SDL Error: " << SDL_GetError() << std::endl;
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    return success;
}

bool loadMedia()
{
    //Loading success flag
    bool success = true;

    //Open the font
    //使用 TTF_OpenFont 加载字体。这需要输入字体文件的路径和我们要渲染的点尺寸
    // gFont = TTF_OpenFont( "lazy.ttf", 28 );
    // if( gFont == nullptr )
    // {
    //     std::cout << "Failed to load lazy font! SDL_ttf Error:" << TTF_GetError() << std::endl;
    //     success = false;
    // }
    // else
    // {
    //     //Render text r,g,b,a
    //     SDL_Color textColor = { 0, 0, 0, 0 };
    //     /*
    //     如果字体加载成功，我们将使用加载方法加载纹理。一般来说，我们希望尽量减少渲染文本的次数。因为我们在整个程序中使用的是同一个文本表面，所以只需渲染一次
    //     */
    //     if( !gTextTexture.loadFromRenderedText( "The quick brown fox jumps over the lazy dog", textColor ) )
    //     {
    //         std::cout << "Failed to render text texture!" << std::endl;
    //         success = false;
    //     }

    // }
    if( !gSplashTexture.loadFromFile( "splash.png" ) )
    {
        std::cout << "Failed to load splash texture image!\n" << std::endl;
        success = false;
    }

    return success;
}

void LTexture::setBlendMode( SDL_BlendMode blending )
{
    //Set blending function
    // SDL_SetTextureBlendMode 允许启用混合模式
    SDL_SetTextureBlendMode( mTexture, blending );
}
        
void LTexture::setAlpha( Uint8 alpha )
{
    //Modulate texture alpha
    // SDL_SetTextureAlphaMod 则允许设置整个纹理的 Alpha 值
    SDL_SetTextureAlphaMod( mTexture, alpha );
}

void close()
{
    // 释放资源  
    // gTextTexture.free();
    // gButtonSpriteSheetTexture.free();
    // gArrowTexture.free();
    gSplashTexture.free();

    //Close game controller
    // 使用完操纵杆后，使用 SDL_JoystickClose 关闭操纵杆
    //Close game controller or joystick with haptics
    if( gGameController != nullptr )
    {
        SDL_GameControllerClose( gGameController );
    }
    if( gJoyHaptic != nullptr )
    {
        SDL_HapticClose( gJoyHaptic );
    }
    if( gJoystick != nullptr )
    {
        SDL_JoystickClose( gJoystick );
    }
    gGameController = nullptr;
    gJoystick = nullptr;
    gJoyHaptic = nullptr;

    //Free global font
    // TTF_CloseFont( gFont );
    // gFont = nullptr;

    //Destroy window
    SDL_DestroyRenderer( gRenderer );
    SDL_DestroyWindow( gWindow );
    gRenderer = nullptr;
    gWindow = nullptr;

    //Quit SDL subsystems
    // TTF_Quit();
    IMG_Quit();
    SDL_Quit();
}


int main( int argc, char* args[] )
{
    //Start up SDL and create window
    if( !init() )
    {
        std::cout << "Failed to initialize!" << std::endl;
    }
    else
    {
        //Load media
        if( !loadMedia() )
        {
            std::cout << "Failed to load media!" << std::endl;
        }
        else
        {
            //Hack to get window to stay up
            SDL_Event e; 
            bool quit = false; 

            //Angle of rotation
            double degrees = 0;

            //Flip type
            SDL_RendererFlip flipType = SDL_FLIP_NONE;

            //Current rendered texture
            LTexture* currentTexture = nullptr;

            //Normalized direction
            /*
            跟踪 x 和 y 方向。如果 x 等于-1，则操纵杆的 x 位置指向左侧。如果 x 为 +1，则 x 位置朝右。
            操纵杆的 y 位置为正则向上，负则向下，因此 y = +1 表示向上，y = -1 表示向下。如果 x 或 y 为 0，则表示处于死区和中心位置。
            */
            int xDir = 0;
            int yDir = 0;

            //While application is running
            while( !quit )
            {
                //Handle events on queue
                while( SDL_PollEvent( &e ) != 0 )
                {
                    //User requests quit
                    if( e.type == SDL_QUIT )
                    {
                        quit = true;
                    }
                    /*
                    使控制器在按下按钮时晃动
                    首先，检查游戏控制器界面是否已加载。如果已加载，以 75% 的强度使控制器震动 500 毫秒。强度从 0 到 0xFFFF 十六进制。同样，这一切都记录在头文件中。
                    如果加载的是触觉设备，则调用 SDL_HapticRumblePlay，它将接收触觉设备、强度（百分比）和隆隆声持续时间。
                    在这里，每当 SDL_JoyButtonEvent 发生时，让控制器以 75% 的强度发出半秒钟的隆隆声。
                    */
                    else if( e.type == SDL_JOYAXISMOTION )
                    {
                        //Use game controller
                        if( gGameController != nullptr )
                        {
                            //Play rumble at 75% strength for 500 milliseconds
                            if( SDL_GameControllerRumble( gGameController, 0xFFFF * 3 / 4, 0xFFFF * 3 / 4, 500 ) != 0 )
                            {
                                std::cout << "Warning: Unable to play game contoller rumble!" << SDL_GetError() << std::endl;
                            }
                        }
                        //Use haptics
                        else if( gJoyHaptic != nullptr )
                        {
                            //Play rumble at 75% strength for 500 milliseconds
                            if( SDL_HapticRumblePlay( gJoyHaptic, 0.75, 500 ) != 0 )
                            {
                                std::cout << "Warning: Unable to play haptic rumble!" << SDL_GetError() << std::endl;
                            }
                        }
                    }
                }

                //Clear screen
                SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
                SDL_RenderClear( gRenderer );

                
                gSplashTexture.render(0, 0);
                
                //Update screen
                SDL_RenderPresent( gRenderer );
            }
        }
    }

    //Free resources and close SDL
    close();

    return 0;
}



